import { revocationHandler, RevocationHandlerOptions } from '../../../../src/server/auth/handlers/revoke.js';
import { OAuthServerProvider, AuthorizationParams } from '../../../../src/server/auth/provider.js';
import { OAuthRegisteredClientsStore } from '../../../../src/server/auth/clients.js';
import { OAuthClientInformationFull, OAuthTokenRevocationRequest, OAuthTokens } from '../../../../src/shared/auth.js';
import express, { Response } from 'express';
import supertest from 'supertest';
import { AuthInfo } from '../../../../src/server/auth/types.js';
import { InvalidTokenError } from '../../../../src/server/auth/errors.js';
import { MockInstance } from 'vitest';

describe('Revocation Handler', () => {
    // Mock client data
    const validClient: OAuthClientInformationFull = {
        client_id: 'valid-client',
        client_secret: 'valid-secret',
        redirect_uris: ['https://example.com/callback']
    };

    // Mock client store
    const mockClientStore: OAuthRegisteredClientsStore = {
        async getClient(clientId: string): Promise<OAuthClientInformationFull | undefined> {
            if (clientId === 'valid-client') {
                return validClient;
            }
            return undefined;
        }
    };

    // Mock provider with revocation capability
    const mockProviderWithRevocation: OAuthServerProvider = {
        clientsStore: mockClientStore,

        async authorize(client: OAuthClientInformationFull, params: AuthorizationParams, res: Response): Promise<void> {
            res.redirect('https://example.com/callback?code=mock_auth_code');
        },

        async challengeForAuthorizationCode(): Promise<string> {
            return 'mock_challenge';
        },

        async exchangeAuthorizationCode(): Promise<OAuthTokens> {
            return {
                access_token: 'mock_access_token',
                token_type: 'bearer',
                expires_in: 3600,
                refresh_token: 'mock_refresh_token'
            };
        },

        async exchangeRefreshToken(): Promise<OAuthTokens> {
            return {
                access_token: 'new_mock_access_token',
                token_type: 'bearer',
                expires_in: 3600,
                refresh_token: 'new_mock_refresh_token'
            };
        },

        async verifyAccessToken(token: string): Promise<AuthInfo> {
            if (token === 'valid_token') {
                return {
                    token,
                    clientId: 'valid-client',
                    scopes: ['read', 'write'],
                    expiresAt: Date.now() / 1000 + 3600
                };
            }
            throw new InvalidTokenError('Token is invalid or expired');
        },

        async revokeToken(_client: OAuthClientInformationFull, _request: OAuthTokenRevocationRequest): Promise<void> {
            // Success - do nothing in mock
        }
    };

    // Mock provider without revocation capability
    const mockProviderWithoutRevocation: OAuthServerProvider = {
        clientsStore: mockClientStore,

        async authorize(client: OAuthClientInformationFull, params: AuthorizationParams, res: Response): Promise<void> {
            res.redirect('https://example.com/callback?code=mock_auth_code');
        },

        async challengeForAuthorizationCode(): Promise<string> {
            return 'mock_challenge';
        },

        async exchangeAuthorizationCode(): Promise<OAuthTokens> {
            return {
                access_token: 'mock_access_token',
                token_type: 'bearer',
                expires_in: 3600,
                refresh_token: 'mock_refresh_token'
            };
        },

        async exchangeRefreshToken(): Promise<OAuthTokens> {
            return {
                access_token: 'new_mock_access_token',
                token_type: 'bearer',
                expires_in: 3600,
                refresh_token: 'new_mock_refresh_token'
            };
        },

        async verifyAccessToken(token: string): Promise<AuthInfo> {
            if (token === 'valid_token') {
                return {
                    token,
                    clientId: 'valid-client',
                    scopes: ['read', 'write'],
                    expiresAt: Date.now() / 1000 + 3600
                };
            }
            throw new InvalidTokenError('Token is invalid or expired');
        }

        // No revokeToken method
    };

    describe('Handler creation', () => {
        it('throws error if provider does not support token revocation', () => {
            const options: RevocationHandlerOptions = { provider: mockProviderWithoutRevocation };
            expect(() => revocationHandler(options)).toThrow('does not support revoking tokens');
        });

        it('creates handler if provider supports token revocation', () => {
            const options: RevocationHandlerOptions = { provider: mockProviderWithRevocation };
            expect(() => revocationHandler(options)).not.toThrow();
        });
    });

    describe('Request handling', () => {
        let app: express.Express;
        let spyRevokeToken: MockInstance;

        beforeEach(() => {
            // Setup express app with revocation handler
            app = express();
            const options: RevocationHandlerOptions = { provider: mockProviderWithRevocation };
            app.use('/revoke', revocationHandler(options));

            // Spy on the revokeToken method
            spyRevokeToken = vi.spyOn(mockProviderWithRevocation, 'revokeToken');
        });

        afterEach(() => {
            spyRevokeToken.mockRestore();
        });

        it('requires POST method', async () => {
            const response = await supertest(app).get('/revoke').send({
                client_id: 'valid-client',
                client_secret: 'valid-secret',
                token: 'token_to_revoke'
            });

            expect(response.status).toBe(405);
            expect(response.headers.allow).toBe('POST');
            expect(response.body).toEqual({
                error: 'method_not_allowed',
                error_description: 'The method GET is not allowed for this endpoint'
            });
            expect(spyRevokeToken).not.toHaveBeenCalled();
        });

        it('requires token parameter', async () => {
            const response = await supertest(app).post('/revoke').type('form').send({
                client_id: 'valid-client',
                client_secret: 'valid-secret'
                // Missing token
            });

            expect(response.status).toBe(400);
            expect(response.body.error).toBe('invalid_request');
            expect(spyRevokeToken).not.toHaveBeenCalled();
        });

        it('authenticates client before revoking token', async () => {
            const response = await supertest(app).post('/revoke').type('form').send({
                client_id: 'invalid-client',
                client_secret: 'wrong-secret',
                token: 'token_to_revoke'
            });

            expect(response.status).toBe(400);
            expect(response.body.error).toBe('invalid_client');
            expect(spyRevokeToken).not.toHaveBeenCalled();
        });

        it('successfully revokes token', async () => {
            const response = await supertest(app).post('/revoke').type('form').send({
                client_id: 'valid-client',
                client_secret: 'valid-secret',
                token: 'token_to_revoke'
            });

            expect(response.status).toBe(200);
            expect(response.body).toEqual({}); // Empty response on success
            expect(spyRevokeToken).toHaveBeenCalledTimes(1);
            expect(spyRevokeToken).toHaveBeenCalledWith(validClient, {
                token: 'token_to_revoke'
            });
        });

        it('accepts optional token_type_hint', async () => {
            const response = await supertest(app).post('/revoke').type('form').send({
                client_id: 'valid-client',
                client_secret: 'valid-secret',
                token: 'token_to_revoke',
                token_type_hint: 'refresh_token'
            });

            expect(response.status).toBe(200);
            expect(spyRevokeToken).toHaveBeenCalledWith(validClient, {
                token: 'token_to_revoke',
                token_type_hint: 'refresh_token'
            });
        });

        it('includes CORS headers in response', async () => {
            const response = await supertest(app).post('/revoke').type('form').set('Origin', 'https://example.com').send({
                client_id: 'valid-client',
                client_secret: 'valid-secret',
                token: 'token_to_revoke'
            });

            expect(response.header['access-control-allow-origin']).toBe('*');
        });
    });
});
